Обработка текстовой информации является, вероятно, одной из самых распространенных задач в современном программировании, и C# предоставляет для ее решения широкий набор средств: отдельные символы, массивы символов, изменяемые и неизменяемые строки и регулярные выражения.

Символы

Ключевое слово char используется для объявления экземпляра структуры System.Char, используемой .NET Framework для представления символа Юникода. Значение объекта Char представляет собой 16-разрядное числовое (порядковое) значение.

Символы в кодировке Юникода используются для представления большинства письменных языков в мире.

Тип

Диапазон

Размер

Тип платформы .NET

char

От U+0000 до U+FFFF

Символ Юникода (16-разрядный)

System.Char


Литералы

Константы типа char могут быть записаны в виде символьных литералов, шестнадцатеричной escape-последовательности или представления Юникода. Также можно выполнить приведение целочисленных кодов символов. В следующем примере четыре переменные char инициализируются с помощью одного символа X:


char[] chars = new char[4];

chars[0] = 'X';        // Character literal
chars[1] = '\x0058';   // Hexadecimal
chars[2] = (char)88;   // Cast from integral type
chars[3] = '\u0058';   // Unicode

foreach (char c in chars)
{
    Console.Write(c + " ");
}
// Output: X X X X



Преобразования

Тип char может быть неявно преобразован в тип ushort, int, uint, long, ulong, float, double или decimal. Тем не менее, неявные преобразования из других типов в тип char не предусмотрены.

Тип System.Char предоставляет несколько статических методов для работы со значениями char.

На значениях типа char определены операции отношения: <,>,>=,<=,==,!=.


char[] C = { 'C', 'B', 'A', '1', 'k', 'T' };
char min = 'C';
foreach(char  ch in C)
{
    if (min > ch) min = ch;
}
Console.WriteLine("Минимальный символ '{0}' с кодом {1}",min, (int)min);
foreach (char ch in C)
{
     Console.WriteLine("Символ '{0}' с кодом {1}", ch, (int)ch);
}


Символьный тип char предназначен для хранения символов в кодировке Unicode. Символьный тип относится к встроенным типам данных C# и соответствует стандартному классу Char библиотеки .NET из пространства имен System. В этом классе определены статические методы, позволяющие задать вид и категорию символа, а также преобразовать символ в верхний или нижний регистр и в число. Некоторые методы приведены в таблилце 6.2.


Таблица 6.2. Некоторые методы класса System.Char

Метод

Описание

GetNumericValue

Возвращает числовое значение символа, если он является цифрой, и –1 в противном случае

IsControl

Возвращает true, если символ является управляющим

IsDigit

Возвращает true, если символ является десятичной цифрой

IsLetter

Возвращает true, если символ является буквой

IsLower

Возвращает true, если символ задан в нижнем регистре

IsUpper

Возвращает true, если символ записан в верхнем регистре

IsWhiteSpace

Возвращает true, если символ является пробельным (пробел, перевод строки и возврат каретки)

Parse

Преобразует строку в символ (строка должна состоять из одного символа)

ToLower

Преобразует символ в нижний регистр

MaxValue, MinValue

Возвращают символы с максимальным и минимальным кодами (эти символы не имеют видимого представления)


В листинге 6.6 продемонстрировано использование этих методов.


using System;

namespace Example_1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                char b = 'B', c = '\x63', d = '\u0032';                      // 1
                Console.WriteLine("{0}  {1}  {2}", b, c, d);
                Console.WriteLine("{0}  {1}  {2}",
                     char.ToLower(b), char.ToUpper(c), char.GetNumericValue(d));

                char a;
                do                                                           // 2
                {
                    Console.Write("Введите символ: ");
                    a = char.Parse(Console.ReadLine());
                    Console.WriteLine("Введен символ {0}, его код – {1}",
                                        a, (int)a);
                    if (char.IsLetter(a)) Console.WriteLine("Буква");
                    if (char.IsUpper(a)) Console.WriteLine("Верхний рег.");
                    if (char.IsLower(a)) Console.WriteLine("Нижний рег.");
                    if (char.IsControl(a)) Console.WriteLine("Управляющий");
                    if (char.IsNumber(a)) Console.WriteLine("Число");
                    if (char.IsPunctuation(a)) Console.WriteLine("Разделитель");
                } while (a != 'q');
            }
            catch
            {
                Console.WriteLine("Возникло исключение");
                return;
            }
        }
    }
}

Листинг 6.6. Использование методов класса System.Char


Результат работы программы:


Массивы символов

Массив символов, как и массив любого иного типа, построен на основе базового класса Array. Применение этих методов позволяет эффективно решать некоторые задачи. Простой пример приведен в листинге 6.7.


using System;

namespace Example_2
{
    class Program
    {
        static void Main(string[] args)
        {
            char[] a = { 'с', 'и', 'м', 'в', 'о', 'л' };         // 1
            char[] b = "наступает новый год".ToCharArray();      // 2
                
            PrintArray( "Исходный массив а:", a );

            int pos = Array.IndexOf( a, 'с' );
            a[pos] = 'С';
            PrintArray( "Измененный массив а:", a );

            PrintArray( "Исходный массив b:", b );
            Array.Reverse( b );
            PrintArray( "Измененный массив b:", b );
        }

        public static void PrintArray( string header, Array a )
        {
            Console.WriteLine( header );
            foreach ( object x in a ) Console.Write( x );
            Console.WriteLine("\n");
        }
    }
}

Листинг 6.7. Работа с массивом символов


Результат работы программы:


Строки типа string

Тип string, предназначенный для работы со строками символов в кодировке Unicode, является встроенным типом C#. Ему соответствует базовый класс System.String библиотеки .NET.

Создать строку можно несколькими способами:

string s; // инициализация отложена
string t = "qqq"; // инициализация строковым литералом
string u = new string(' ', 20); // конструктор создает строку из 20 пробелов
char[] a = { '0', '0', '0' }; // массив для инициализации строки
string v = new string( a ); // создание из массива символов

Для строк определены следующие операции:

• присваивание (=);

• проверка на равенство (==);

• проверка на неравенство (!=);

• обращение по индексу ([]);

• сцепление (конкатенация) строк (+).

Несмотря на то, что строки являются ссылочным типом данных, на равенство и неравенство проверяются не ссылки, а значения строк. Строки равны, если имеют одинаковое количество символов и совпадают посимвольно.

Обращаться к отдельному элементу строки по индексу можно только для получения значения, но не для его изменения. Это связано с тем, что строки типа string относятся к так называемым неизменяемым типам данных. Методы, изменяющие содержимое строки, на самом деле создают новую копию строки. Неиспользуемые "старые" копии автоматически удаляются сборщиком мусора.

В классе System.String предусмотрено множество методов, полей и свойств, позволяющих выполнять со строками практически любые действия. Некоторые элементы класса приведены в таблилце 6.3.


Таблица 6.3. Некоторые элементы класса System.String

Название

Вид

Описание

Compare

Статический метод

Сравнение двух строк в лексикографическом (алфавитном) порядке. Разные реализации метода позволяют сравнивать строки и подстроки с учетом и без учета регистра и особенностей национального представления дат и т. д.

Concat

Статический метод

Конкатенация строк. Метод допускает сцепление произвольного числа строк

Copy

Статический метод

Создание копии строки

Format

Статический метод

Форматирование в соответствии с заданными спецификаторами формата (см. далее)

IndexOf,
IndexOfAny,
LastIndexOf,
LastIndexOfAny

Методы

Определение индексов первого и последнего вхождения заданной подстроки или любого символа из заданного набора

Insert

Метод

Вставка подстроки в заданную позицию

Join

Статический метод

Слияние массива строк в единую строку. Между элементами массива вставляются разделители (см. далее)

Length

Свойство

Длина строки (количество символов)

Split

Метод

Разделение строки на элементы, используя заданные разделители. Результаты помещаются в массив строк

Substring

Метод

Выделение подстроки, начиная с заданной позиции

ToCharArray

Метод

Преобразование строки в массив символов

ToLower,ToUpper

Методы

Преобразование символов строки к нижнему или верхнему регистру


Пример применения методов приведен в листинге 6.8.


using System;

namespace Example_3
{
    class Program
    {
        static void Main(string[] args)
        {
            string s = "прекрасная королева Изольда";
            Console.WriteLine(s);
            string sub = s.Substring(3).Remove(12, 2);                  // 1
            Console.WriteLine(sub);

            string[] mas = s.Split(' ');                                // 2
            string joined = string.Join("! ", mas);
            Console.WriteLine(joined);

            Console.WriteLine("Введите строку");
            string x = Console.ReadLine();                              // 3
            Console.WriteLine("Вы ввели строку " + x);

            double a = 12.234;
            int b = 29;
            Console.WriteLine(" a = {0,6:C}  b = {1,2:X}", a, b);       // 4
            Console.WriteLine(" a = {0,6:0.##}  a = {1,5:0.# ' руб. '}",
                               a, b);                                   // 5

        }
    }
}


Листинг 6.8. Работа со строками типа string


Результат работы программы:


Форматирование строк

В операторе 4 из листинга 6.7 неявно применяется метод Format, который заменяет все вхождения параметров в фигурных скобках значениями соответствующих переменных из списка вывода. После номера параметра можно задать минимальную ширину поля вывода, а также указать спецификатор формата, который определяет форму представления выводимого значения.

В общем виде параметр задается следующим образом:

{n [,m[:спецификатор_формата]]}

Здесь n — номер параметра. Параметры нумеруются с нуля, нулевой параметр заменяется значением первой переменной из списка вывода, первый параметр — второй переменной, и т. д. Параметр m определяет минимальную ширину поля, которое отводится под выводимое значение. Если выводимому числу достаточно меньшего количества позиций, неиспользуемые позиции заполняются пробелами. Если числу требуется больше позиций, параметр игнорируется.

Спецификатор формата, как явствует из его названия, определяет формат вывода значения. Например, спецификатор C (Currency) означает, что параметр должен форматироваться как валюта с учетом национальных особенностей представления, а спецификатор Х (Hexadecimal) задает шестнадцатеричную форму представления выводимого значения.

В операторе 5 используются так называемые пользовательские шаблоны форматирования. Если приглядеться, в них нет ничего сложного: после двоеточия задается вид выводимого значения посимвольно, причем на месте каждого символа может стоять либо #, либо 0. Если указан знак #, на этом месте будет выведена цифра числа, если она не равна нулю. Если указан 0, будет выведена любая цифра, в том числе и 0.


Работа со строками

Строки и класс System.String

Довольно большое количество задач, которые могут встретиться при разработке приложений, так или иначе связано с обработкой строк - парсинг веб-страниц, поиск в тексте, какие-то аналитические задачи, связанные с извлечением нужной информации из текста и т.д. Поэтому в этом плане работе со строками уделяется особое внимание.

В языке C# строковые значения представляет тип string, а вся функциональность работы с данным типом сосредоточена в классе System.String. Собственно string является псевдонимом для класса System.String. Объекты этого класса представляют текст, как последовательность символов Unicode. Максимальный размер объекта String может составлять в памяти 2 ГБ, или около 1 миллиард символов.

Создавать сроки можно, как используя переменную типа string и присваивая ей значение, так и применяя один из конструкторов класса String:

1 string s1 = "hello";
2 string s2 = null;
3
4 string s3 = new String('a', 6); // результатом будет строка "aaaaaa"
5 string s4 = new String(new char[]{'w', 'o', 'r', 'l', 'd'});

Конструктор String имеет различное число версий. Так, вызов конструктора new String('a', 6) создаст строку "aaaaaa". И так как строка представляет ссылочный тип, то может хранить значение null.

Строка как набор символов

Так как строка хранит коллекцию символов, в ней определен индексатор для доступа к этим символам.

Применяя индексатор, мы можем обратиться к строке как к массиву символов и получить по индексу любой из ее символов:

1 string s1 = "hello";
2 char ch1 = s1[1]; // символ 'e'
3 Console.WriteLine(ch1);
4 Console.WriteLine(s1.Length);

Используя свойство Length, как и в обычном массиве, можно получить длину строки.

Основные методы строк

Основная функциональность класса String раскрывается через его методы, среди которых можно выделить следующие:

• Compare: сравнивает две строки с учетом текущей культуры (локали) пользователя

• CompareOrdinal: сравнивает две строки без учета локали

• Contains: определяет, содержится ли подстрока в строке

• Concat: соединяет строки

• CopyTo: копирует часть строки или всю строку в другую строку

• EndsWith: определяет, совпадает ли конец строки с подстрокой

• Format: форматирует строку

• IndexOf: находит индекс первого вхождения символа или подстроки в строке

• Insert: вставляет в строку подстроку

• Join: соединяет элементы массива строк

• LastIndexOf: находит индекс последнего вхождения символа или подстроки в строке

• Replace: замещает в строке символ или подстроку другим символом или подстрокой

• Split: разделяет одну строку на массив срок

• Substring: извлекает из строки подстроку, начиная с указанной позиции

• ToLower: переводит все символы строки в нижний регистр

• ToUpper: переводит все символы строки в верхний регистр

• Trim: удаляет начальные и конечные пробелы из строки

Разберем работу этих методов.

Операции со строками

Конкатенация

Конкатенация строк или объединение может производиться как с помощью операции +, так и с помощью метода Concat:

1 string s1 = "hello";
2 string s2 = "world";
3 string s3 = s1 + " " + s2; // результат: строка "hello world"
4 string s4 = String.Concat(s3, "!!!"); // результат: строка "hello world!!!"
5
6 Console.WriteLine(s4);

Метод Concat является статическим методом класса String, принимающим в качестве параметров две строки. Также имеются другие версии метода, принимающие другое количество параметров.

Для объединения строк также может использоваться метод Join:

1 string s5 = "apple";
2 string s6 = "a day";
3 string s7 = "keeps";
4 string s8 = "a doctor";
5 string s9 = "away";
6 string[] values = new string[] { s5, s6, s7, s8, s9 };
7
8 String s10 = String.Join(" ", values);
9 // результат: строка "apple a day keeps a doctor away"

Метод Join также является статическим. Использованная выше версия метода получает два параметра: строку-разделитель (в данном случае пробел) и массив строк, которые будут соединяться и разделяться разделителем.

Сравнение строк

Для сравнения строк применяется статический метод Compare:


1 string s1 = "hello";
2 string s2 = "world";
3 
4 int result = String.Compare(s1, s2);
5 if (result<0)
6  {
7    Console.WriteLine("Строка s1 перед строкой s2");
8  }
9  else if (result > 0)
10 {
11    Console.WriteLine("Строка s1 стоит после строки s2");
12 }
13 else
14 {
15    Console.WriteLine("Строки s1 и s2 идентичны");
16 }
17 // результатом будет "Строка s1 перед строкой s2"

Данная версия метода Compare принимает две строки и возвращает число. Если первая строка по алфавиту стоит выше второй, то возвращается число меньше нуля. В противном случае возвращается число больше нуля. И третий случай - если строки равны, то возвращается число 0.

В данном случае так как символ h по алфавиту стоит выше символа w, то и первая строка будет стоять выше.

Поиск в строке

С помощью метода IndexOf мы можем определить индекс первого вхождения отдельного символа или подстроки в строке:

1 string s1 = "hello world";
2 char ch = 'o';
3 int indexOfChar = s1.IndexOf(ch); // равно 4
4 Console.WriteLine(indexOfChar);
5
6 string subString = "wor";
7 int indexOfSubstring = s1.IndexOf(subString); // равно 6
8 Console.WriteLine(indexOfSubstring);

Подобным образом действует метод LastIndexOf, только находит индекс последнего вхождения символа или подстроки в строку.

Еще одна группа методов позволяет узнать начинается или заканчивается ли строка на определенную подстроку. Для этого предназначены методы StartsWith и EndsWith. Например, из массива Text нам необходимо вывести все строки с окончанием «нь»:


string[] Text = {"Конь", "Огонь", "Лень", "Олень", "Дождь", "Новость"};
foreach (string s in Text) 
  if (s.EndsWith("нь")) Console.WriteLine(s);


Разделение строк

С помощью функции Split мы можем разделить строку на массив подстрок. В качестве параметра функция Split принимает массив символов или строк, которые и будут служить разделителями. Например, подсчитаем количество слов в сроке, разделив ее по пробельным символам:

1 string text = "И поэтому все так произошло";
2
3 string[] words = text.Split(new char[] { ' ' });
4
5 foreach (string s in words)
6 {
7 Console.WriteLine(s);
8 }

Это не лучший способ разделения по пробелам, так как во входной строке у нас могло бы быть несколько подряд идущих пробелов и в итоговый массив также бы попадали пробелы, поэтому лучше использовать другую версию метода:

string[] words = text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

Второй параметр StringSplitOptions.RemoveEmptyEntries говорит, что надо удалить все пустые подстроки.

Обрезка строки

Для обрезки начальных или концевых символов используется функция Trim:

1 string text = " hello world ";
2
3 text = text.Trim(); // результат "hello world"
4 text = text.Trim(new char[] { 'd', 'h' }); // результат "ello worl"

Функция Trim без параметров обрезает начальные и конечные пробелы и возвращает обрезанную строку. Чтобы явным образом указать, какие начальные и конечные символы следует обрезать, мы можем передать в функцию массив этих символов.

Эта функция имеет частичные аналоги: функция TrimStart обрезает начальные символы, а функция TrimEnd обрезает конечные символы.

Обрезать определенную часть строки позволяет функция Substring:

1 string text = "Хороший день";
2 // обрезаем начиная с третьего символа
3 text = text.Substring(2);
4 // результат "роший день"
5 Console.WriteLine(text);
6 // обрезаем сначала до последних двух символов
7 text = text.Substring(0, text.Length - 2);
8 // результат "роший де"
9 Console.WriteLine(text);

Функция Substring также возвращает обрезанную строку. В качестве параметра первая использованная версия применяет индекс, начиная с которого надо обрезать строку. Вторая версия применяет два параметра - индекс начала обрезки и длину вырезаемой части строки.

Вставка

Для вставки одной строки в другую применяется функция Insert:

1 string text = "Хороший день";
2 string subString = "замечательный ";
3
4 text = text.Insert(8, subString);
5 Console.WriteLine(text);

Первым параметром в функции Insert является индекс, по которому надо вставлять подстроку, а второй параметр - собственно подстрока.

Удаление строк

Удалить часть строки помогает метод Remove:

1 string text = "Хороший день";
2 // индекс последнего символа
3 int ind = text.Length - 1;
4 // вырезаем последний символ
5 text = text.Remove(ind);
6 Console.WriteLine(text);
7
8 // вырезаем первые два символа
9 text = text.Remove(0, 2);

Первая версия метода Remove принимает индекс в строке, начиная с которого надо удалить все символы. Вторая версия принимает еще один параметр - сколько символов надо удалить.

Замена

Чтобы заменить один символ или подстроку на другую, применяется метод Replace:

1 string text = "хороший день";
2
3 text = text.Replace("хороший", "плохой");
4 Console.WriteLine(text);
5
6 text = text.Replace("о", "");
7 Console.WriteLine(text);

Во втором случае применения функции Replace строка из одного символа "о" заменяется на пустую строку, то есть фактически удаляется из текста. Подобным способом легко удалять какой-то определенный текст в строках.

Смена регистра

Для приведения строки к верхнему и нижнему регистру используются соответственно функции ToUpper() и ToLower():

1 string hello = "Hello world!";
2
3 Console.WriteLine(hello.ToLower()); // hello world!
4 Console.WriteLine(hello.ToUpper()); // HELLO WORLD!

Строки типа StringBuilder

Возможности, предоставляемые классом string, широки, однако требование неизменности его объектов может оказаться неудобным. В этом случае для работы со строками применяется класс StringBuilder, определенный в пространстве имен System.Text и позволяющий изменять значение своих экземпляров.

Как известно, строки string в .NET являются неизменяемыми, а поэтому проводить с ними операцию конкатенации в больших количествах не эффективно. Это значит, что следующий код имеет весьма серьезные проблемы с нагрузкой на память:


string s = string.Empty;
for (int i = 0; i < 100; i++)
 {
    s += "T";
 } 


Что же плохого в этом коде? А то, что на каждой итерации создается строка длиною на единицу больше чем на предыдущем шаге с последующим копированием символов из старой строки. Таким образом, суммарное количество задействованных символов равно:

Данная формула есть не что иное, как сумма арифметической прогрессии:

То есть такой сценарий конкатенации требует памяти пропорционально O(n2), n — длина строки. Для устранения проблем в подобном коде мы используем класс StringBuilder, зная, что операции с ним не приводят к таким растратам памяти как со String. Фактически StringBuilder представляет собой изменяемую строку.


var strB = new StringBuilder();
for (int i = 0; i < 100; i++)
{
  strB.Append("T");
}
string str = strB.ToString();


Методы и свойства класса StringBuilder

Рассмотрим наиболее часто используемые методы и свойства.

Свойства

Имя

Описание

Capacity

Возвращает или задает максимальное число знаков, которое может содержаться в памяти, назначенной текущим экземпляром.

Chars[Int32]

Возвращает или задает символ на указанной позиции в данном экземпляре.

Length

Возвращает или задает длину текущего объекта StringBuilder.

MaxCapacity

Возвращает максимальную емкость данного экземпляра.


Методы

Имя

Описание

Append(String)

Добавляет копию указанной строки к данному экземпляру.

AppendFormat(String, Object, Object, Object)

Добавляет к данному экземпляру строку, возвращаемую в результате обработки строки составного формата, содержащей ноль или более элементов формата. Каждый элемент формата заменяется строковым представлением любого из трех аргументов.

AppendFormat(String, Object[])

Добавляет к данному экземпляру строку, возвращаемую в результате обработки строки составного формата, содержащей ноль или более элементов формата. Каждый элемент формата заменяется строковым представлением соответствующего аргумента в массиве параметров.

AppendLine()

Добавляет знак завершения строки по умолчанию в конец текущего объекта StringBuilder.

AppendLine(String)

Добавляет копию указанной строки и знак завершения строки по умолчанию в конец текущего объекта StringBuilder.

Clear()

Удаляет все символы из текущего экземпляра StringBuilder.

CopyTo(Int32, Char[], Int32, Int32)

Копирует символы из указанного сегмента этого экземпляра в указанный массив Char.

Equals(Object)

Определяет, равен ли заданный объект текущему объекту.(Наследуется от Object.)

Equals(StringBuilder)

Возвращает значение, показывающее, равен ли данный экземпляр заданному объекту.

Insert(Int32, String)

Вставляет строку в данный экземпляр на указанную позицию символа.

Insert(Int32, String, Int32)

Вставляет одну или более копий указанной строки в данный экземпляр на указанную позицию символа.

Remove(Int32, Int32)

Удаляет указанный диапазон символов из данного экземпляра.

Replace(String, String)

Замещает все вхождения указанной строки в данном экземпляре на другую указанную строку.

ToString()

Преобразует значение данного экземпляра в String.(Переопределяет Object.ToString().)



 int x = 11;
 int y = 2017;
 StringBuilder z = new StringBuilder();
 z.AppendFormat("Дата 7-{0}-{1}", x, y);
 Console.WriteLine(z);



Вопросы и задания для самостоятельной работы студента

1. Опишите основные методы и свойства класса System.Array

2. Какие ограничения имеет оператор foreach по сравнению с оператором for?

3. Что такое кодировка Unicode?

4. Какие средства работы с отдельными символами предоставляет C#?

5. Опишите основные методы и свойства класса string.

6. Можно ли изменить длину строки после того, как память под нее была выделена?

7. Какое основное ограничение имеет класс string?

8. Какие существуют возможности форматирования строк?

9. Перечислите спецификации формата.

10. Опишите пользовательский формат вещественного числа с двумя ведущими нулями и тремя знаками после запятой.

11. Изучите по справочной системе свойства и методы стандартного класса System.Array.

12. Изучите по справочной системе свойства и методы стандартного класса System.String.

13. Изучите по справочной системе свойства и методы стандартного класса System.Char.

14. Изучите по справочной системе свойства и методы стандартного класса System.Text.StringBuilder.

15. Изучите разделы стандарта C#, касающиеся массивов.

16. Изучите разделы стандарта C#, касающиеся символов.

17. Изучите разделы стандарта C#, касающиеся строк.